#include "SceneManager.h"
#include <time.h>

int SceneManager::theta_x;
int SceneManager::theta_y;
int SceneManager::crt_render_mode;
int SceneManager::crt_shape;
int SceneManager::crt_rs;
int SceneManager::crt_vs;
double SceneManager::cyl_height = 3;
double SceneManager::cyl_ray = 1;
double SceneManager::sph_ray = 3;
MSphere *SceneManager::sphere = NULL;
MTorus *SceneManager::donut = NULL;
Shape *SceneManager::shape = NULL;
MCylinder * SceneManager::cylinder = NULL;
MCone *SceneManager::cone = NULL;
MMesh *SceneManager::monster = NULL;
MMesh *SceneManager::dragon = NULL;
MMesh *SceneManager::teapot = NULL;
MCube *SceneManager::cube = NULL;
MStar *SceneManager::star = NULL;

SceneManager::~SceneManager(void)
{

}

void SceneManager::my_setup() 
{
	theta_x = 0;
	theta_y = 0;
	crt_render_mode = GL_LINE_LOOP;
	crt_shape = STAR;
	crt_rs = 20;
	crt_vs = 10;
	sphere = MSphere::makeSphere(1,10,10);
	donut = MTorus::makeTorus(1.2,0.2,40,15);
	cylinder = MCylinder::makeCylinder(1,1,10,10);
	cone = MCone::makeCone(1,2,2,10);
	cube = MCube::makeCube(1,4);
	star = MStar::makeStar(1,3,0.5);
	monster = MMesh::makeMesh("monsterdance.obj");
	dragon = MMesh::makeMesh("dragon.obj");
	teapot = MMesh::makeMesh("teapot.obj");
	return;
}

int SceneManager::my_glmain(int argc, char **argv) {

	glutInit( &argc, argv ) ;

	/* we define these setup procedures */
	my_glutsetup() ;  
	my_glsetup() ;
	my_setup();

	/* go into the main event loop */
	glutMainLoop() ;

	return(0) ;
}

void  SceneManager::my_glutsetup() {
	/* specify display mode -- here we ask for a double buffer and RGB coloring */
	/* NEW: tell display we care about depth now */
	glutInitDisplayMode (GLUT_DOUBLE |GLUT_RGB |GLUT_DEPTH);

	/* make a 400x400 window with the title of "Stitcher" placed at the top left corner */
	glutInitWindowSize(400,400);
	glutInitWindowPosition(0,0);
	glutCreateWindow("Stitcher");

	/*initialize callback functions */
	glutDisplayFunc( my_display );
	glutReshapeFunc( my_reshape ); 
	glutMouseFunc( my_mouse);
	glutKeyboardFunc(my_keyboard);

	/*just for fun, uncomment the line below and observe the effect :-)
	Do you understand now why you shd use timer events, 
	and stay away from the idle event?  */
	//glutIdleFunc( my_idle );

	glutTimerFunc( 20000, my_timeout, 0);/* schedule a my_TimeOut call with the ID 0 in 20 seconds from now */
	return ;
}

void  SceneManager::my_glsetup() {
	/* specifies a background color: black in this case */
	glClearColor(0,0,0,0) ;

	/* NEW: now we have to enable depth handling (z-buffer) */
	glEnable(GL_DEPTH_TEST);

	/* NEW: setup for 3d projection */
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity();
	// perspective view
	gluPerspective( 20.0, 1.0, 1.0, 100.0);
	return;
}

void SceneManager::my_display()
{
	/* clear the buffer */
	/* NEW: now we have to clear depth as well */
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) ;

	glMatrixMode(GL_MODELVIEW) ;
	glLoadIdentity();
	gluLookAt(0.0, 5.0, 25.0,  // x,y,z coord of the camera 
		0.0, 0.0, 0.0,  // x,y,z coord of the origin
		0.0, 1.0, 0.0); // the direction of up (default is y-axis)

	glRotatef(theta_y,0,1,0);
	glRotatef(theta_x,1,0,0);
	switch(crt_shape){
	case HOUSE: draw_house(); break;
	case CUBE: 
		if(cube) {
			cube->drawCube(draw_poly,draw_line);
		}
		break; /*TODO: replace with draw_cube_smart*/
	case CYLINDER: 
		if(cylinder) {
			cylinder->drawCylinder(draw_triangle,draw_line);
		}
		break;
	case CONE: 
		if(cone) {
			cone->drawCone(draw_triangle,draw_line);
		}
		break;
	case SPHERE:
		if(sphere) {
			sphere->drawSphere(draw_triangle,draw_line);  
			if(!sphere->isComplete())
				glutTimerFunc( 10, my_timeout, 1);
		}
		break;
	case TORUS: 
		if(donut) {
			donut->drawTorus(draw_triangle,draw_line);  
			if(!donut->isComplete())
				glutTimerFunc( 10, my_timeout, 1);
		}
		break;
	case MESH1: 
		if(monster) {
			monster->drawMesh(draw_poly, draw_line);
		}
		break;
	case MESH2: 
		if(dragon) {
			dragon->drawMesh(draw_poly, draw_line);
		}
		break;
	case MESH3: 
		if(teapot) {
			teapot->drawMesh(draw_poly, draw_line);
		}
		break;
	case STAR:
		if(star) {
			star->drawStar(draw_triangle,draw_line);
		}
		break;
	default: break;
	}

	/* buffer is ready */
	glutSwapBuffers();

	return ;
}


void SceneManager::my_timeout(int id)
{
	switch (id) {
	case 1:	//event id 1, sphere
		glutPostRedisplay();
		break;
	}
}

void SceneManager::my_keyboard(unsigned char key, int x, int y)
{
	switch( key ) {
	case 'y':
	case 'Y': {
		theta_y = (theta_y+2) %360;
		glutPostRedisplay(); 
			  }; break;
	case 'x':
	case 'X': {
		theta_x = (theta_x+2) %360;
		glutPostRedisplay(); 
			  }; break;
	case 'B':
	case 'b': {
		crt_shape = CUBE;
		shape = cube;
		glutPostRedisplay();
			  }; break;
	case 'H':
	case 'h': {
		crt_shape = HOUSE;
		shape = NULL;
		glutPostRedisplay();
			  }; break;
	case 'S':
	case 's':
		crt_shape = SPHERE;
		shape = sphere;
		glutPostRedisplay();
		break;
	case 'M':
	case 'm':
		crt_shape = TORUS;
		shape = donut;
		glutPostRedisplay();
		break;
	case 'C':
	case 'c':
		crt_shape = CYLINDER;
		shape = cylinder;
		glutPostRedisplay();
		break;
	case 'N':
	case 'n':
		crt_shape = CONE;
		shape = cone;
		glutPostRedisplay();
		break;
	case '1':
		crt_shape = MESH1;
		shape = monster;
		glutPostRedisplay();
		break;
	case '2':
		crt_shape = MESH2;
		shape = dragon;
		glutPostRedisplay();
		break;
	case '3':
		crt_shape = MESH3;
		shape = teapot;
		glutPostRedisplay();
		break;
	case 'q': 
	case 'Q':
		cleanupExit();
	case 'v': 
	case 'V':
		if(shape) {
			shape->bNormal = !shape->bNormal;
			glutPostRedisplay();
		}
		break;
	case 't':
	case 'T':
		crt_shape = STAR;
		glutPostRedisplay();
		break;
	case '+':
		if(shape) {
			shape->incrementSlice();
			glutPostRedisplay();
		}
		break;
	case '-':
		if(shape) {
			shape->decrementSlice();
			glutPostRedisplay();
		}
		break;
	case '>':
		if(shape) {
			shape->incrementStack();
			glutPostRedisplay();
		}
		break;
	case '<':
		if(shape) {
			shape->decrementStack();
			glutPostRedisplay();
		}
		break;
	default: break;
	}

	return ;
}

void SceneManager::my_idle(void) {
	theta_y = (theta_y+2) %360;
	glutPostRedisplay();
	return ;
}

void SceneManager::my_reshape(int w, int h) {
	/* define viewport -- x, y, (origin is at lower left corner) width, height */
	glViewport (0, 0, min(w,h), min(w,h));

	return;
}

void SceneManager::my_mouse(int b, int s, int x, int y) {
	switch( b ) {

	case GLUT_LEFT_BUTTON:
		if( s == GLUT_DOWN ) {
			crt_render_mode = GL_LINE_LOOP;
			/* if you're not sure what glutPostRedisplay does for us,
			try commenting out the line below; observe the effect.*/
			glutPostRedisplay();
		}
		if( s == GLUT_UP ) {
		}
		break ;

	case GLUT_RIGHT_BUTTON:
		if ( s == GLUT_DOWN ) {
			crt_render_mode = GL_POLYGON;
			glutPostRedisplay();
		}
		if( s == GLUT_UP ) {
		}
		break ;
	}

	return ;
}

/***********************************
FUNCTION: draw_triangle 
ARGS: - a vertex array
- 3 indices into the vertex array defining a triangular face
- an index into the color array.
RETURN: none
DOES:  helper drawing function; draws one triangle. 
For the normal to work out, follow left-hand-rule (i.e., ccw)
*************************************/
void SceneManager::draw_triangle(const GLfloat vertices[][4], int iv1, int iv2, int iv3, int ic) {
	glBegin(crt_render_mode); 
	{
		glColor3fv(colors[ic]);
		/*note the explicit use of homogeneous coords below: glVertex4f*/
		glVertex4fv(vertices[iv1]);
		glVertex4fv(vertices[iv2]);
		glVertex4fv(vertices[iv3]);
	}
	glEnd();
}

void SceneManager::draw_triangle(const MVertex &p1,  const MVertex &p2, const MVertex &p3, int ic) {
	glBegin(crt_render_mode); 
	{
		glColor3fv(colors[ic]);
		/*note the explicit use of homogeneous coords below: glVertex4f*/
		glVertex4fv(p1.point);
		glVertex4fv(p2.point);
		glVertex4fv(p3.point);
	}
	glEnd();
}

void SceneManager::draw_line(const MVertex &p1,  const MVertex &p2,int ic) {
	glBegin(GL_LINES); 
	{
		glColor3fv(colors[ic]);
		/*note the explicit use of homogeneous coords below: glVertex4f*/
		glVertex4fv(p1.point);
		glVertex4fv(p2.point);
	}
	glEnd();
}

/***********************************
FUNCTION: draw_quad 
ARGS: - a vertex array
- 4 indices into the vertex array defining a quad face
- an index into the color array.
RETURN: none
DOES:  helper drawing function; draws one quad. 
For the normal to work out, follow left-hand-rule (i.e., counter clock
wise) 
*************************************/
void SceneManager::draw_quad(const GLfloat vertices[][4], int iv1, int iv2, int iv3, int iv4, int ic) {
	glBegin(crt_render_mode); 
	{
		glColor3fv(colors[ic]);
		/*note the explicit use of homogeneous coords below: glVertex4f*/
		glVertex4fv(vertices[iv1]);
		glVertex4fv(vertices[iv2]);
		glVertex4fv(vertices[iv3]);
		glVertex4fv(vertices[iv4]);
	}
	glEnd();
}

void SceneManager::draw_house()
{
	draw_triangle(vertices_house,0,1,2,RED);
	draw_triangle(vertices_house,0,2,3,GREEN);
	draw_triangle(vertices_house,0,3,4,WHITE);
	draw_triangle(vertices_house,0,4,1,GREY);


	draw_quad(vertices_house,2,1,5,6, BLUE);
	draw_triangle(vertices_house,2,6,3, CYAN);
	draw_triangle(vertices_house,3,6,7, CYAN);
	draw_triangle(vertices_house,3,7,8, YELLOW);
	draw_triangle(vertices_house,8,3,4, YELLOW);
	draw_triangle(vertices_house,4,8,1, MAGENTA);
	draw_triangle(vertices_house,1,8,5, MAGENTA);

}

/***********************************
FUNCTION: draw_cube_brute 
ARGS: none
RETURN: none
DOES: helper build function; 
defines a hollow cube without a bottom or a top
************************************/
/*TODO: add bottom and top face triangles*/
void SceneManager::draw_cube_brute()
{
	draw_triangle(vertices_cube_brute, 4,5,1,BLUE);
	draw_triangle(vertices_cube_brute, 0,4,1,BLUE);
	draw_triangle(vertices_cube_brute, 5,6,2,CYAN);
	draw_triangle(vertices_cube_brute, 1,5,2,CYAN);
	draw_triangle(vertices_cube_brute, 3,2,6,YELLOW);
	draw_triangle(vertices_cube_brute, 7,3,6,YELLOW);
	draw_triangle(vertices_cube_brute, 0,3,7,MAGENTA);
	draw_triangle(vertices_cube_brute, 4,0,7,MAGENTA);
}

/***********************************
FUNCTION: draw_param_quad 
ARGS: - a 50 by 50 by 4 vertex array
- 2 indices into the vertex array defining the top left of a quad face
- an index into the color array.
RETURN: none
DOES:  helper drawing function; draws one quad. 
*************************************/
/*TODO: redefine and implement as needed, according to your data structures */
void SceneManager::draw_param_quad(GLfloat vertices[][50][4], int line, int col, int ic) {

}

void SceneManager::draw_poly(const vector<MVertex> &v1, int ic) {
	glBegin(crt_render_mode); 
	{
		glColor3fv(colors[ic]);
		for(int i =0; i<v1.size();i++) {
		/*note the explicit use of homogeneous coords below: glVertex4f*/
		glVertex4fv(v1[i].point);
		}
	}
	glEnd();
}

template <class T>
void DELETEO(T *x) {
	if(x) 
		delete x;
}

void SceneManager::cleanupExit() {
	DELETEO(sphere);
	DELETEO(donut);
	DELETEO(cone);
	DELETEO(cube);
	DELETEO(cylinder);
	DELETEO(monster);
	DELETEO(dragon);
	DELETEO(teapot);
	DELETEO(star);
	exit(0);
}








